package com.zhang.yunfu_norevocation.comm.Utils;

import cn.com.infosec.nettsa.NetTSAClient;
import com.alibaba.fastjson.JSON;
import com.ccsp.sdk.SDFFactory;
import com.ccsp.sdk.bean.GlobalData;
import com.ccsp.sdk.bean.P7VerifyData;
import com.ccsp.sdk.bean.SM2refSignature;
import com.ccsp.sdk.bean.SessionKeyContext;
import com.ccsp.sdk.crypto.CryptoException;
import com.ccsp.sdk.crypto.HsmClient;
import com.ccsp.sdk.crypto.SignatureClient;
import com.ccsp.sdk.crypto.impl.CCSPClient;
import com.sansec.util.encoders.Base64;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

@Slf4j
//@Component
public class KingNetUtils
{
    private static CCSPClient ccspClient;
    private static HsmClient hsmClient;  // 做加解密服务和P1验签服务
    private static SignatureClient signatureClient; // 做P7签名验签服务
    private static final String SM4_KEY = "hlwyyxxSM4_Random_DEK";  // TODO 换成自己的【SM4密钥】名称
    private static final String SM2_KEY = "hlwyyxxSM2_Standard"; // TODO 换成自己的【SM2密钥】名称
    private static final String HMAC_KEY = "hlwyyxxSM4_Random_DEK"; // TODO HMAC内部密钥可使用【SM4密钥】或者重新申请
    static final String P7_KEY = "hlw_sign"; // 证书标签，用于p7
    static final String SIGNCERT_PATH = "cert/【签名证书】0001_SM2_signcert.cer";
    static final int port = 8151;
    static final NetTSAClient client = new NetTSAClient("19.193.200.171", 2000, 2000);


    public static void main(String[] args) throws CryptoException {
        java.net.URL resource = KingNetUtils.class.getClassLoader().getResource("ini/swsds.ini");
        if (resource == null) {
            throw new RuntimeException("Configuration file 'ini/swsds.ini' not found in classpath!");
        }

        String configPath = resource.getFile();
        ccspClient = SDFFactory.getInstance(configPath);
    }

    static {
        try {
//            java.net.URL resource = KingNetUtils.class.getClassLoader().getResource("ini/swsds.ini");
//            if (resource == null) {
//                throw new RuntimeException("Configuration file 'config/swsds.ini' not found in classpath!");
//            }
//
//            String configPath = resource.getFile();
//            log.info("configPath: {}", configPath);

            // 1. 加载CCSP配置文件（swsds.ini为SDK核心配置，含HSM连接信息、算法参数等）
            ccspClient = SDFFactory.getInstance("/www/olinq/basic/ini/swsds.ini");
            // 2. 登录HSM（通过应用名+密码认证，确保只有授权应用可访问HSM资源）
            ccspClient.CCSP_LoginbyAppNameAndPwd("hlwyyxx", "HLWyy12#$");
            // 3. 获取子客户端（HsmClient负责基础加解密，SignatureClient负责P7签名）
            hsmClient = CCSPClient.getHsmClient();
            signatureClient = CCSPClient.getSignatureClient();
        } catch (Exception e) {
            // 初始化失败直接抛出运行时异常，避免后续使用空客户端导致更复杂问题
            throw new RuntimeException("KingNetUtils initialization failed", e);
        }
    }

    /**
     * 获取 signatureClient
     * 补：获取P7签名验签客户端（用于P7格式的SM2签名验签）
     * @return signatureClient
     */
    public SignatureClient getSignatureClient() {
        return signatureClient;
    }

    /**
     * 获取 hsmClient
     * 补：获取基础加解密客户端（用于SM3、SM4、HMAC、随机数等操作）
     * @return hsmClient
     */
    public HsmClient getHsmClient() {
        return hsmClient;
    }

    /**
     * 获取 原始数据的SM3 的 Base64编码
     * @param data 原始数据
     * @return SM3 的 Base64编码（长度固定为44字符，因32字节Base64编码后为44字符）
     * @throws CryptoException 哈希计算失败（如HSM连接异常、数据为空）
     */
    public String getSM3BS64(String data) throws CryptoException {
        Object sm3Context = hsmClient.CCSP_HashInit(GlobalData.SGD_SM3, null, null);
        hsmClient.CCSP_HashUpdate(sm3Context, data.getBytes());
        byte[] sm3Result = hsmClient.CCSP_HashFinal(sm3Context, null);
        return Base64.toBase64String(sm3Result);
    }

    /**
     * 获取 原始数据的SM3 的字节数组
     * @param data 原始数据
     * @return SM3 的字节数组
     */
    public byte[] getSM3Bytes(String data) throws CryptoException {
        Object sm3Context = hsmClient.CCSP_HashInit(GlobalData.SGD_SM3, null, null);
        hsmClient.CCSP_HashUpdate(sm3Context, data.getBytes());
        return hsmClient.CCSP_HashFinal(sm3Context, null);
    }


    /**
     * 使用内部SM2密钥对原始数据进行SM3WithSM2签名
     * @param oriData 原始数据
     * @return 字节数组的签名值
     *
     * 补：先对数据做 SM3 哈希（减少数据量，提高签名效率），
     * 再用 HSM 中存储的 SM2 私钥签名，私钥永不出 HSM，安全性极高：
     */
    public byte[] SM3WithSM2Sign(String oriData) throws CryptoException {
        // 先对原始数据进行 SM3 计算 得到 sm3Result字节数组
        Object sm3Context = hsmClient.CCSP_HashInit(GlobalData.SGD_SM3, null, null);
        hsmClient.CCSP_HashUpdate(sm3Context, oriData.getBytes());
        byte[] sm3Result = hsmClient.CCSP_HashFinal(sm3Context, null);
        // 对 sm3Result字节数组 进行签名
        SM2refSignature signature = hsmClient.CCSP_InternalSign_ECC(SM2_KEY, sm3Result);
        return signature.encode();
    }

    /**
     * 使用内部SM2密钥对SM3WithSM2签名值进行验签
     * @param oriDataSM3 原始数据的SM3值
     * @param signature 签名值
     * @return boolean类型验签结果
     */
    public boolean SM3WithSM2Verify(byte[] oriDataSM3, byte[] signature) throws CryptoException {
        SM2refSignature sign = new SM2refSignature();
        try {
            sign.decode(signature); // 这里需要signature是128字节的原始数据
        } catch (CryptoException e) {
            throw new CryptoException("Invalid signature format: " + e.getMessage(), e);
        }
        return hsmClient.CCSP_InternalVerify_ECC(SM2_KEY, oriDataSM3, sign);
    }

    /**
     * P1验签（搭配Ukey做登录）
     * @param oriData 原文数据
     * @param signDataBS64 Base64编码的签名值
     * @param pemCert  pem格式的证书
     * @return boolean类型验签结果
     */
    public boolean P1Verify(String oriData, String signDataBS64, String pemCert) {
        // 判断证书中是否包含-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----，如果包含则去掉
        String pattern = "(?s)\\s*-----BEGIN CERTIFICATE-----\\s*(.*?)\\s*-----END CERTIFICATE-----\\s*";
        pemCert = pemCert.replaceAll(pattern, "$1").trim();
        System.out.println(pemCert);
        byte[] pemCertBytes = Base64.decode(pemCert);
        byte[] signData = Base64.decode(signDataBS64);
        try {
            // CCSP_P1Verify方法如果签名值和原文验签成功返回true，否则报错
            return hsmClient.CCSP_P1Verify(pemCertBytes, signData, oriData.getBytes(), GlobalData.SGD_SM3);
        } catch (CryptoException e) {
            System.out.println(e.getMessage());  // TODO 当前简单打印错误信息，可以换成自己系统中的日志
            return false;
        }
    }


    /**
     * P7验签（搭配Ukey做登录）
     * @param oriData 原文数据
     * @param signDataBS64 Base64编码的签名值
     * @param pemCert pem格式的证书
     * @return boolean类型验签结果
     * 注意：使用该方法时必须到租户平台上导入根证书；前端Ukey对随机数签名后输出的就是Base64的签名值，后端拿到直接当成signDataBS64参数放入函数中
     */
    public boolean P7Verify(String oriData, String signDataBS64, String pemCert) {
        byte[] signData = Base64.decode(signDataBS64);
        try {
            // CCSP_SAF_SM2_DecodeSignedData_Detached方法验签成功返回0，验签失败报错
            P7VerifyData p7VerifyData = signatureClient.CCSP_SAF_SM2_DecodeSignedData_Detached(signData, oriData.getBytes());
            if (p7VerifyData.getResult() == 0) { // 校验成功
                // 检查p7签名值中的证书和平台存储的证书是否一致
                byte[] signerCert = p7VerifyData.getSignerCert();
                String pattern = "(?s)\\s*-----BEGIN CERTIFICATE-----\\s*(.*?)\\s*-----END CERTIFICATE-----\\s*";
                pemCert = pemCert.replaceAll(pattern, "$1").trim();
                byte[] userCert = Base64.decode(pemCert);
                return Arrays.equals(signerCert, userCert);
            }
            return false;
        } catch (CryptoException e) {
            System.out.println(e.getMessage());  // TODO 生产环境中可以注释掉或者替换为日志
            return false;
        }
    }

    /**
     * SM4加密/对称加密
     * @param oriData 原文数据
     * @param iv 初始向量(可以设为null)
     * @param algID 算法标识
     * @return 密文数据
     * 一般 algID使用 GlobalData.SGD_SM4_ECB或GlobalData.SGD_SM4_CBC
     */
    public byte[] SM4Encrypt(String oriData, byte[] iv, int algID) throws CryptoException {
        SessionKeyContext skc = new SessionKeyContext();
        skc.setKeyID(SM4_KEY.getBytes());
        skc.setAlgId(algID);
        return hsmClient.CCSP_EncryptWithPadding(skc, algID, iv, oriData.getBytes());
    }



    /**
     * SM4解密
     * @param cipherData SM4加密后的密文数据
     * @param iv 初始向量(与加密时保持一致)
     * @param algID 算法标识(与加密时保持一致)
     * @return 原文的byte[]类型，若返回为null则解密失败
     */
    public byte[] SM4Decrypt(byte[] cipherData, byte[] iv, int algID) {
        SessionKeyContext skc = new SessionKeyContext();
        skc.setKeyID(SM4_KEY.getBytes());
        skc.setAlgId(algID);
        try {
            return hsmClient.CCSP_DecryptWithPadding(skc, algID, iv, cipherData);
        } catch (CryptoException e) {
            System.out.println(e.getMessage());
            return null;
        }
    }

    /**
     *  SM4解密
     * @param cipherData 密文数据
     * @param iv 初始向量(与加密时保持一致)
     * @param algID 算法标识(与加密时保持一致)
     * @return String类型原文，若返回null则解密失败
     */
    public String SM4DecryptToString(byte[] cipherData, byte[] iv, int algID) {
        byte[] decrypted = SM4Decrypt(cipherData, iv, algID);
        if (decrypted == null) {
            return null;
        }
        return new String(decrypted, StandardCharsets.UTF_8);
    }


    /**
     * 计算HMAC
     * @param algID 算法标识（一般为 GlobalData.SGD_SM3）
     * @param oriData String类型的原文数据，数据不能超过10K
     * @return Base64编码的HMAC值
     */
    public String CalculateHMAC(int algID, String oriData) throws CryptoException {
        return CalculateHMAC(algID, oriData.getBytes());
    }


    /**
     * 计算HMAC
     * @param algID 算法标识（一般为 GlobalData.SGD_SM3）
     * @param oriData byte[]类型的原文数据，数据大小不能超过10K
     * @return Base64编码的HMAC值
     */
    public String CalculateHMAC(int algID, byte[] oriData) throws CryptoException {
        SessionKeyContext skc = new SessionKeyContext();
        skc.setKeyID(HMAC_KEY.getBytes());
        skc.setAlgId(algID);
        byte[] hmac = hsmClient.CCSP_HMAC(skc, algID, oriData);
        return Base64.toBase64String(hmac);
    }

    /**
     * 验证HMAC(完整性校验)
     * @param algID 算法标识（一般为 GlobalData.SGD_SM3）
     * @param oriData byte[]类型的原文数据，，数据大小不能超过10K
     * @param hmacBS64 Base64编码的HMAC值
     * @return boolean类型验证结果
     */
    public boolean VerifyHMAC(int algID, byte[] oriData, String hmacBS64) throws CryptoException {
        String hmac = CalculateHMAC(algID, oriData);
        return hmac.equals(hmacBS64);
    }


    /**
     * 验证HMAC(完整性校验)
     * @param algID 算法标识（一般为 GlobalData.SGD_SM3）
     * @param oriData String类型的原文数据，数据不能超过10K
     * @param hmacBS64 Base64编码的HMAC值
     * @return boolean类型验证结果
     */
    public boolean VerifyHMAC(int algID, String oriData, String hmacBS64) throws CryptoException {
        String hmac = CalculateHMAC(algID, oriData);
        return hmac.equals(hmacBS64);
    }

    /**
     * 生成指定长度的Base64编码的随机数
     * @param length 随机数长度
     * @return Base64编码的随机数
     */
    public String GenerateRandomBS64(int length) throws CryptoException {
        byte[] random = hsmClient.CCSP_GenerateRandom(length);
        return Base64.toBase64String( random);
    }

    /**
     * 获取SM4加密后的Base64编码数据
     * @param originalData 原文数据
     * @return Base64编码数据
     */
    public String getEncryptedBase64CBC(String originalData) throws Exception {
        if (StringUtils.isBlank(originalData)){
            return "";
        }
        byte[] encryptedDataCBC = SM4Encrypt(originalData, null, com.ccsp.sdk.bean.GlobalData.SGD_SM4_CBC);
        return com.sansec.util.encoders.Base64.toBase64String(encryptedDataCBC);
    }

    /**
     * 获取SM4解密后的数据
     */
    public String getDecryptBase64CBC(String data) throws Exception {
        if (StringUtils.isBlank(data)){
            return "";
        }
        byte[] dataByte = Base64.decode(data);
        return SM4DecryptToString(dataByte, null, com.ccsp.sdk.bean.GlobalData.SGD_SM4_CBC);
    }


    public boolean verifyHMACThrowCatch(String oriData, String hmacBS64) {
        if(StringUtils.isBlank(oriData) && StringUtils.isBlank(hmacBS64)){
            return true;
        }
        String hmac = null;
        try {
            hmac = CalculateHMAC(com.ccsp.sdk.bean.GlobalData.SGD_SM3, oriData);
        } catch (CryptoException e) {
            throw new RuntimeException(e);
        }
        return hmac.equals(hmacBS64);
    }

    public String calculateHMACByOriData(String oriData) throws CryptoException {
        if(StringUtils.isBlank(oriData)){
            return "";
        }
        return CalculateHMAC(com.ccsp.sdk.bean.GlobalData.SGD_SM3, oriData.getBytes());
    }

    /**
     * P7签名（时间戳）  TODO：个人标识医生
     * @param certLabel  证书标签
     * @param filePath   证书文件路径（注意：文件权限）
     * @param oriData    待签名的byte[]原始数据
     * @return
     * 对应描述： 调用密码机和医院的数字证书进行签名
     */
    public static byte[] P7Sign(String certLabel, String filePath, byte[] oriData) throws IOException, CryptoException {
        try (InputStream is = KingNetUtils.class.getClassLoader().getResourceAsStream(SIGNCERT_PATH)) {
            if (is == null) {
                log.error("签名证书不存在：" + SIGNCERT_PATH);
                throw new RuntimeException("签名证书不存在：" + SIGNCERT_PATH);
            }
            byte[] certBytes = inputStreamToByteArray(is);
            return signatureClient.CCSP_SAF_SM2_EncodeSignedData(
                    certLabel.getBytes(),
                    GlobalData.SGD_SM2_SIGN,
                    certBytes,
                    GlobalData.SGD_SM3,
                    oriData);
        }
    }

    /**
     * P7验证
     */
    public static boolean P7Verify(byte[] p7SignData) {
        try {
            P7VerifyData p7VerifyData = signatureClient.CCSP_SAF_SM2_DecodeSignedData(p7SignData);
            if (p7VerifyData.getResult() == 0) {
                return true;
            }
        } catch (CryptoException e) {
            log.error("P7验签异常", e);
            return false;
        }
        return false;
    }

    /**
     * 获取时间戳令牌Base64格式字符串
     * @param plainData（文件和签名值一起作为输入）
     */
    public static String GetTimeStampToken2Base64(String plainData) {
        if (plainData == null) {
            log.error("原始数据为空，无法生成时间戳");
            return null;
        }
        String tokenJsonStr = client.CreateSM2TSReqForTSToken(plainData, port);
        if (tokenJsonStr == null) {
            log.error("时间戳服务返回空");
            return null;
        }
        String tokenBase64 = JSON.parseObject(tokenJsonStr).getString("data");
        return StringUtils.isBlank(tokenBase64) ? null : tokenBase64;
    }

    public static String GetTimeStampToken2Json(String plainData) {
        String tokenBase64 = GetTimeStampToken2Base64(plainData);
        if (StringUtils.isBlank(tokenBase64)) {
            log.error("获取时间戳令牌失败");
            return null;
//            throw new RuntimeException("获取时间戳令牌失败");
        }

        // 解析时间戳令牌，获取json格式信息
        String parseResult = client.parseTSToken(tokenBase64, port);
        return StringUtils.isBlank(parseResult) ? null : parseResult;
    }
    /**
     * 验证时间戳令牌
     *
     *    verifyByTSToken(String plain, String tokenBase64, int serverPort)）
     *          * plain - 原文
     *          * tokenBase64 - base64编码的TimestampToken签名值
     *          * serverPort - 服务端口
     *          * @return 返回:
     *          * json字符串,格式 {"data":{},"msg":"","status":""},
     *          * status为0表示验签成功，
     *          * data为签名信息结构为{"signAlg":"","time":""},
     *          * signAlg为签名算法,time为时间戳
     *
     */
    public static String VerifyTimeStampToken(String plainData, String tokenBase64) {
        return client.verifyByTSToken(plainData, tokenBase64, port);
    }
    /**
     * 解析时间戳令牌 parseResult
     */
    public static String GetTimeStampInfo(String plainData) {
        String tokenBase64 = GetTimeStampToken2Base64(plainData);
        String parseResult = client.parseTSToken(tokenBase64, port);
        return parseResult.isEmpty() ? null : parseResult;
    }

    /**
     * 获取解析后数据中的时间戳
     * @param plainData
     * @return
     */
    public static String GetTimeStamp(String plainData) {
        String parseResult = GetTimeStampInfo(plainData);
        if (parseResult.isEmpty()){
            log.error("获取时间戳失败");
            return null;
//            throw new RuntimeException("获取时间戳失败");
        }
        String timestamp = JSON.parseObject(parseResult).getJSONObject("data").getString("createTime");
        return timestamp.isEmpty() ? null : timestamp;
    }

    /**
     * 获取签名值
     * @param plainData
     */
    public static String GetSignature(String plainData) {
        return GetTimeStampToken2Base64(plainData);
    }

    /**
     * 将 InputStream 转换为 byte[]
     * @param is 输入流
     * @return 转换后的字节数组
     * @throws
     */
    private static byte[] inputStreamToByteArray(InputStream is) throws IOException {
        // 字节输出流，用于临时存储读取的数据
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        // 缓冲区大小
        byte[] buffer = new byte[4096];
        int len;
        // 循环读取流数据到缓冲区，直到流结束（len = -1）
        while ((len = is.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        // 强制将缓冲区剩余数据写入输出流
        bos.flush();
        // 将输出流中的数据转换为字节数组并返回
        return bos.toByteArray();
    }
}
